/* INCLUDES ******************************************************************/
#include "ses_adc.h"
#include "ses_common.h"
#include "ses_scheduler.h"

/* DEFINES & MACROS **********************************************************/

#define ADC_TEMP_TABLE_SIZE  (sizeof(adcTempTable) / sizeof(uint16_t))
#define ADC_TEMP_MIN           0    /* minimum temp [dC]      */
#define ADC_TEMP_STEP         50    /* stepping in table [dC] */



/* PRIVATE VARIABLES *********************************************************/

/* callback for ADC  [challenge]*/
static adc_callback_t adcCallback = NULL;
/* conversion table for temperatue values */
static const uint16_t adcTempTable[] = { 888, 762, 655, 560, 479, 410, 351,
		301, 259, 223, 192 };
/* temp storage for mean computation */
static adcMean_t adcMean = { 0, 0, 0 };

/* FUNCTION DECLARATION ******************************************************/

void adc_ExecCallback();

/* FUNCTION DEFINITION *******************************************************/

void adc_init(void) {

	/* Set AD ports to input */
	DDR(PORTF) &= ~((1 << ADC_TEMP_PIN) | (1 << ADC_LIGHT_PIN) | (1
			<< ADC_JOYSTICK_PIN) | (1 << ADC_MIC_NEG_PIN) | (1
			<< ADC_MIC_POS_PIN));

	/* Clear the pullup bit */
	PORTF &= ~((1 << ADC_TEMP_PIN) | (1 << ADC_LIGHT_PIN) | (1
			<< ADC_JOYSTICK_PIN) | (1 << ADC_MIC_NEG_PIN) | (1
			<< ADC_MIC_POS_PIN));

	/* Disable ADC power reduction */
	PRR0 &= ~(1 << PRADC);

	/* Set Reference Voltage */
	ADMUX |= ADC_VREF_SRC;

	/* Enable ADC and set Prescaler */
	ADCSRA |= (1 << ADEN) | ADC_PRESCALE;
}

uint16_t adc_read(uint8_t adc_channel) {
	/* Check for Valid Channel */
	if (adc_channel >= ADC_NUM)
		return 0;

	/* Set Channel */
	ADMUX = (ADMUX & (~ADC_MUX_MASK)) | adc_channel;

	/* Start Conversion */
	ADCSRA |= (1 << ADSC);

	/* Wait for result */
	while ((ADCSRA & (1 << ADSC)))
		__asm("nop");

	/* Return result */
	return ADCL + (ADCH << 8);
}

/** ADC interrupt callbacks  [challenge]*/
ISR(ADC_vect) {
	/* In case there are still messurements to take */
	if (adcMean.passNo < (ADC_NUM_AVG - 1)) {

		/* Add value from last conversion */
		adcMean.intermediateVal += (ADCL + (ADCH << 8));

		/* Start conversion and enable interrupt */
		ADCSRA |= (1 << ADSC) | (1 << ADIE);

		/* Increase Counter */
		adcMean.passNo++;

	} else { /* Otherwise comute the mean */
		/* Add value from last conversion */
		adcMean.intermediateVal += (ADCL + (ADCH << 8));

		/* Compute mean and store in adcMean*/
		adcMean.meanVal = adcMean.intermediateVal / ADC_NUM_AVG;

		/* Reset temp variables */
		adcMean.intermediateVal = 0;
		adcMean.passNo = 0;

		/* Disable adc interrupt */
		ADCSRA &= ~(1 << ADIE);

		/* Execute the adc callback function */
		scheduler_add(adc_ExecCallback, 0);
	}

}

/**
 * Wrapper to schedule the adcCallback function.
 * Executes the function stored in adcCallback.
 */
void adc_ExecCallback() {
	adc_callback_t temp;

	if (adcCallback != NULL) {
		temp = adcCallback;
		adcCallback = NULL;

		temp(adcMean.meanVal);
	}
}

void adc_asyncRead(uint8_t adc_channel, adc_callback_t cb) {
	/* Check for Valid Channel */
	if (adc_channel >= ADC_NUM)
		return;

	/* Cancel job if other asyncReads are running, indicated by adcCallback */
	if (adcCallback != NULL)
		return;

	/* Store callback function */
	adcCallback = cb;

	/* Set Channel */
	ADMUX = (ADMUX & (~ADC_MUX_MASK)) | adc_channel;

	/* Start Conversion and enable interrupt */
	ADCSRA |= (1 << ADSC) | (1 << ADIE);
}

void adc_disable(void) {
	ADCSRA &= ~(1 << ADEN);
	PRR0 |= (1 << PRADC);
}

/* NOTE This only works in case of monotonically descending values in adcTempTable */
int16_t adc_convertTemp(uint16_t val) {
	uint8_t i = 0;

	/* Find lower bound value */
	while (adcTempTable[i] > val)
		i++;

	/* Check if we found the exact value */
	if (adcTempTable[i] == val)
		return i * ADC_TEMP_STEP;

	/* Check if found index is 0 -> return minimum */
	if (i == 0)
		return adcTempTable[i];

	/* If val is between two fix values, interpolate */
	uint16_t tableStep = (adcTempTable[i - 1] - adcTempTable[i]);
	uint16_t valueStep = val - adcTempTable[i];

	return i * ADC_TEMP_STEP - (ADC_TEMP_STEP * valueStep) / tableStep;
}

